﻿/**********************************************************************
 * 
 *   Sample code of using C#4.0 to manipulate IE of auto-filling 
 *      alipay.com interactively
 *      
 *   C#4.0 操作IE浏览器自动填表支付宝网站完整实例代码
 * 
 *   Make sure install VS2010 before 
 *      compiling and running this sample code
 *
 *   请首先安装VS2010然后方可编译并运行
 *   
 *   Author:
 *      stainboyx@hotmail.com
 *      
 *   References:
 *      http://support.microsoft.com/kb/249232
 *      http://topic.csdn.net/u/20100830/19/a6de3d7f-ef9d-43d5-8356-ed755eb14ba3.html
 * 
 **********************************************************************/

// Import C:\Program Files\Microsoft.NET\Primary Interop Assemblies\Microsoft.mshtml.dll
// Import C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\UIAutomationClient.dll
// Import C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\UIAutomationTypes.dll


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using mshtml;

namespace AlipayRunner
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // 申明一个变量用于记录IE主窗口句柄
            AutomationElement ieframe = null;

            // 关闭所有IE进程
            Parallel.ForEach(Process.GetProcessesByName("iexplore"), p => p.Kill());
            Console.WriteLine("All IE process(es) were killed.");

            // 申明一个变量用于等待IE主窗口弹出
            using (EventWaitHandle waiter = new ManualResetEvent(false))
            {
                AutomationEventHandler handler = (sender, e) =>
                {
                    var source = sender as AutomationElement;
                    if (source != null && source.Current.ClassName == "IEFrame")
                    {
                        ieframe = source;
                        waiter.Set();
                        Console.WriteLine("IE main window handle was found, HWND={0}, ProcessId={1}.", ieframe.Current.NativeWindowHandle, ieframe.Current.ProcessId);
                    }
                };
                // 注册WindowOpenEvent
                Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, handler);
                Console.WriteLine("WindowOpenedEvent was registered on AutomationElement.RootElement.");

                // 启动IE，转向支付宝，请确保IE是默认浏览器
                Process.Start("iexplore", "http://alipay.com/");
                Console.WriteLine("Ready to launch alipay.com.");

                // 等待IE窗口弹出
                Console.WriteLine("Waiting for IE main window handler pop-up...");
                waiter.WaitOne();

                // 反注册WindowOpenEvent
                Automation.RemoveAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, handler);
                Console.WriteLine("WindowOpenedEvent was removed on AutomationElement.RootElement.");
            }

            // 获取HTMLDocument对象
            IHTMLDocument2 document = GetDomFromIEFrame(ieframe);
            Console.WriteLine("Document was found, url={0}", document.url);

            // 等待document加载完毕
            while (document.readyState != "complete")
            {
                Console.WriteLine("Waiting for document ready...");
                Thread.Sleep(500);
            }
            Console.WriteLine("Document loaded completely.");


            // 检查打开的页面是否为支付宝首页
            if (!document.title.StartsWith("支付宝"))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Current page might not be Alipay, title={0}", document.title);
                Console.ResetColor();
                goto exit;
            }
            Console.WriteLine("Current page is Alipay");

            // 找到用户名控件，填写tom@123.com
            // <!--<input name="logonId" tabIndex="1" class="text text-focus" id="il-user" type="text" value=""/>-->
            var aliuid = (document as IHTMLDocument3).getElementById("il-user") as IHTMLInputTextElement;
            aliuid.value = "tom@123.com";
            Console.WriteLine("Input tom@123.com into aliuid textbox,");

            // 找到密码控件，填写123456
            // <!--<object name="_fmu.l._0.p_ie" width="194" height="24" tabIndex="2" id="_fmu.l._0.p_ie" classid="clsid:488A4255-3236-44B3-8F27-FA1AECAA8844" codeBase="https://download.alipay.com/aliedit/aliedit/2401/aliedit.cab#Version=1,1,0,0">-->
            var alipwdPlaceholder = (document as IHTMLDocument3).getElementById("_fmu.l._0.p_ie") as IHTMLObjectElement;

            // 以下代码使用支付宝控件原生方式进行填充，不过无效。。。
            //dynamic alipwd = alipwdPlaceholder.@object;
            //ALIEDITLib.IEditCtrl editCtl = alipwd;
            //editCtl.Crypto(true, 0, "123456");

            // 以下代码使用WM_CHAR方式填充
            IntPtr hwnd;
            IOleInPlaceActiveObject pao = alipwdPlaceholder.@object;
            int hr = pao.GetWindow(out hwnd);
            if (hr != 0)
                Marshal.ThrowExceptionForHR(hr);
            // 找第一个子窗口
            hwnd = (IntPtr)AutomationElement.FromHandle(hwnd).FindFirst(TreeScope.Children, Condition.TrueCondition).Current.NativeWindowHandle;
            // 填充
            foreach (char c in "123456")
            {
                SendMessageW((int)hwnd, WM_CHAR, (int)c, 0);
            }
            Console.WriteLine("Input [{0}] into alipwd textbox({1}).", "123456", hwnd);

            // 找到登录按钮，点击
            // <!--<input tabIndex="3" class="new-btn-login" id="il-smt" type="submit" seed="home-signup" value=""/>-->
            var submit = (document as IHTMLDocument3).getElementById("il-smt") as IHTMLElement;
            submit.click();
            Console.WriteLine("Submit the form.");

        exit:
            Console.WriteLine("Finalizing, click enter key to quit...");
            Console.ReadLine();
        }

        private static IHTMLDocument2 GetDomFromIEFrame(AutomationElement ieframe)
        {
            var shell = GetShellDocObjectViewFromIE(ieframe);
            var document = GetDomFromShellDocObjectView(shell.Current.NativeWindowHandle);
            return document;
        }

        private static IHTMLDocument2 GetDomFromShellDocObjectView(int hWnd)
        {
            object dom = new object();
            Guid riid = new Guid("626FC520-A41E-11CF-A731-00A0C9082637");
            string lpString = "WM_HTML_GETOBJECT";
            int wMsg = RegisterWindowMessageW(lpString);
            if (wMsg == 0)
            {
                throw new Exception("Unable to register windows message WM_HTML_GETOBJECT");
            }
            int lResutl = SendMessageW(hWnd, wMsg, 0, 0);
            if (ObjectFromLresult(lResutl, ref riid, 0, out dom) != 0)
            {
                throw new Win32Exception("Unable to get IE DOM from window message result in GetDomFromShellDocObjectView()");
            }
            return dom as IHTMLDocument2;
        }
        private static AutomationElement GetShellDocObjectViewFromIE(AutomationElement ieframe)
        {
            AutomationElement e = null;

            while (e == null)
                e = ieframe.FindFirst(TreeScope.Descendants,
                        new PropertyCondition(AutomationElement.ClassNameProperty, "Internet Explorer_Server"));

            return e;
        }

        [DllImport("user32")]
        private static extern int SendMessageW(int hwnd, int message, int wParam, int lParam);
        [DllImport("user32")]
        private static extern int RegisterWindowMessageW([MarshalAs(UnmanagedType.LPWStr)] string lpString);
        [DllImport("oleacc")]
        private static extern int ObjectFromLresult(int lResult, ref Guid riid, int wParem, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
        private const int WM_CHAR = 0x0102;

        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), SuppressUnmanagedCodeSecurity, Guid("00000117-0000-0000-C000-000000000046")]
        public interface IOleInPlaceActiveObject
        {
            [PreserveSig]
            int GetWindow(out IntPtr hwnd);
            void ContextSensitiveHelp(int fEnterMode);
            //[PreserveSig]
            //int TranslateAccelerator([In] ref NativeMethods.MSG lpmsg);
            void OnFrameWindowActivate(bool fActivate);
            void OnDocWindowActivate(int fActivate);
            //void ResizeBorder([In] NativeMethods.COMRECT prcBorder, [In] UnsafeNativeMethods.IOleInPlaceUIWindow pUIWindow, bool fFrameWindow);
            void EnableModeless(int fEnable);
        }

    }

}
